package sany.iec.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.openmuc.j60870.*;
import org.openmuc.j60870.ie.*;
import org.springframework.stereotype.Service;
import sany.iec.config.GlobalVariable;
import sany.iec.entity.DataEntity;
import sany.iec.entity.ServerEntity;
import sany.iec.enums.DataCategoryEnum;
import sany.iec.enums.IEC104DataTypeEnum;
import sany.iec.service.IEC104ServerService;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author 托马斯
 * @Date 2025/7/6 18:23
 * @Description IEC104Service
 */
@Service
@Slf4j
public class IEC104ServiceImpl implements IEC104ServerService {

    private final Map<Server, List<Connection>> CON_MAP = new ConcurrentHashMap<>();

    @Override
    public void startServer(ServerEntity data, Server server) throws IOException {
        server.start(new ServerEventListener() {
            @Override
            public ConnectionEventListener connectionIndication(Connection connection) {
                log.info("客户端连接成功: {}", connection.getLocalAddress());
                CON_MAP.putIfAbsent(server, new ArrayList<>());
                CON_MAP.get(server).add(connection); // 添加到活跃连接列表

                return new ConnectionEventListener() {
                    // 当 客户端向服务端发送 ASdu（Application Service Data Unit）数据单元 时，会触发此方法。
                    // 接收遥测、遥信、遥控、遥测变化等实际业务数据。
                    @Override
                    public void newASdu(Connection connection, ASdu aSdu) {
                        InformationObject[] info = aSdu.getInformationObjects();
                        InformationElement[][] elements = info[0].getInformationElements();
                        InformationElement element = elements[0][0];
                        Object value = getValue(element);
                        log.info("{}-{} 接收到数据：COA - {},  IOA - {},  value - {}, type - {}", connection.getLocalAddress(), connection.getLocalPort(),
                                aSdu.getCommonAddress(), info[0].getInformationObjectAddress(), value, aSdu.getTypeIdentification());
                        // 将下发的指令存入缓存
                        sendDataToCache(data, info[0].getInformationObjectAddress(), value, aSdu.getTypeIdentification());
                        try {
                            // 对下发的指令进行相应，不然会连接客户端显示连接超时
                            response(connection, aSdu);
                        } catch (IOException e) {
                            log.error("相应客户端失败。", e);
                        }
                    }

                    // 当 客户端断开连接（主动或被动）时，触发此方法。
                    @Override
                    public void connectionClosed(Connection connection, IOException e) {
                        log.warn("客户端断开连接: {}  {}", connection.getLocalAddress(), connection.getLocalPort());
                        CON_MAP.getOrDefault(server, new ArrayList<>()).remove(connection); // 从列表中移除
                    }

                    // 当连接状态从“未就绪”变为“就绪”时，触发此方法。
                    @Override
                    public void dataTransferStateChanged(Connection connection, boolean b) {
                        log.info("数据状态发生改变: {}  {}", connection.getLocalAddress(), connection.getLocalPort());
                    }
                };
            }

            @Override
            public void serverStoppedListeningIndication(IOException e) {
                log.error("服务器停止监听", e);
            }

            @Override
            public void connectionAttemptFailed(IOException e) {
                log.error("客户端连接失败", e);
            }
        });
    }

    @Override
    public void removeServer(Server server) {
        List<Connection> connections = CON_MAP.get(server);
        for (Connection connection : connections) {
            try {
                connection.close();
            } catch (Exception e) {
                log.error("关闭连接失败", e);
            }
        }
        CON_MAP.remove(server);
    }

    @Override
    public void broadYCValues(Server server, DataEntity data, float value) {
        if (!data.getIec104DataTypeEnum().equals(IEC104DataTypeEnum.M_ME_NC)) {
            // 不是修改遥测数据
            return;
        }
        InformationObject io = new InformationObject(
                data.getAddress(),
                new InformationElement[][]{
                        {
                                new IeShortFloat(value),
                                new IeQuality(false, false, false, false, false)
                        }
                }
        );
        // M_ME_NB_1(普通遥测)：只能传输整数，服务端与客户端需要配合系数 + 偏移进行原始数据的还原。
        // M_ME_TB_1(时标遥测)：只能传输整数，服务端与客户端需要配合系数 + 偏移进行原始数据的还原。（每次发送的数据带有时间）
        // M_ME_NC_1(普通高精度遥测)：直接传输浮点数。
        // M_ME_TC_1(普通高精度带时标遥测)：直接传输浮点数。
        ASdu aSdu = new ASdu(ASduType.M_ME_NC_1, false, CauseOfTransmission.SPONTANEOUS,
                false, false, 0, data.getIec104CommonAddress(), new InformationObject[]{io});
        sendToAllConnections(server, aSdu);
    }

    @Override
    public void broadYXValues(Server server, DataEntity data, boolean value) {
        if (!data.getIec104DataTypeEnum().equals(IEC104DataTypeEnum.M_SP_NA)) {
            // 不是修改遥测数据
            return;
        }
        InformationObject ios = new InformationObject(
                data.getAddress(), // 正确写入每个遥信点的 IOA
                new IeSinglePointWithQuality(
                        value,  // 遥信状态 true/false
                        false, false, false, false // 质量位：无故障
                )
        );
        ASdu aSdu = new ASdu(ASduType.M_SP_NA_1, false, CauseOfTransmission.SPONTANEOUS,
                false, false, 0, 1, ios);
        sendToAllConnections(server, aSdu);
    }

    private void sendToAllConnections(Server server, ASdu aSdu) {
        if (server == null) {
            return;
        }
        List<Connection> connections = CON_MAP.getOrDefault(server, new ArrayList<>());
        for (Connection conn : connections) {
            try {
                if (!conn.isClosed()) {
                    conn.send(aSdu);
                    log.debug("已发送数据至客户端: {}", conn.getLocalAddress());
                } else {
                    log.info("清理无效链接！！！");
                    // 清理无效连接
                    CON_MAP.get(server).remove(conn);
                }
            } catch (IOException e) {
                log.error("发送数据失败: {}", conn.getLocalAddress(), e);
                // 清理异常连接
                CON_MAP.get(server).remove(conn);
            }
        }
    }

    /**
     * 将客户端下发的数据解析后存入Map缓存
     */
    private void sendDataToCache(ServerEntity data, int address, Object value, ASduType type) {
        data.getListData().forEach(d -> {
            if (type.getId() == d.getIec104DataTypeEnum().getCode() && address == d.getAddress()) {
                log.info("excel: {} 端口: {} key: {} value: {} 进入缓存", data.getTableName(), address, d.getKey(), value);
                GlobalVariable.GLOBAL_MAP.put(data.getPort() + ":" + d.getKey(), (Float) value);
            }
        });
    }

    /**
     * 响应客户端下发的数据，避免客户端控制超时
     *
     * @param connection : 链接
     * @param aSdu       : 客户端下发的数据
     */
    private void response(Connection connection, ASdu aSdu) throws IOException {
        // 构造激活终止（ACT_TERM）回应，避免客户端没有得到相应报连接超时或者控制超时
        ASdu actTerm = new ASdu(
                aSdu.getTypeIdentification(),        // 同样类型
                false,
                CauseOfTransmission.ACTIVATION_CON,
                aSdu.isTestFrame(),
                aSdu.isNegativeConfirm(),
                aSdu.getOriginatorAddress(),
                aSdu.getCommonAddress(),
                aSdu.getInformationObjects()
        );

        connection.send(actTerm);
    }

    private Object getValue(InformationElement element) {
        if (element instanceof IeNormalizedValue) {
            int value = ((IeNormalizedValue) element).getUnnormalizedValue();
            log.info("Normalized Value: {}", value);
            return value;
        } else if (element instanceof IeShortFloat) {
            float value = ((IeShortFloat) element).getValue();
            log.info("ShortFloat Value: {}", value);
            return value;
        } else if (element instanceof IeSinglePointWithQuality) {
            boolean value = ((IeSinglePointWithQuality) element).isOn();
            log.info("SinglePointWithQuality Value: {}", value);
            return value ? 1 : 0;
        } else if (element instanceof IeSingleCommand) {
            boolean value = ((IeSingleCommand) element).isCommandStateOn();
            log.info("IeSingleCommand Value: {}", value);
            return value ? 1 : 0;
        } else if (element instanceof IeQualifierOfInterrogation) {
            log.info("总召命令");
        } else if (element instanceof IeQualifierOfCounterInterrogation) {
            log.info("电能量召唤");
        } else if (element instanceof IeDoubleCommand) {
            int value = ((IeDoubleCommand) element).getCommandState().ordinal();
            log.info("IeDoubleCommand Value: {}", value);
            return value;
        } else {
            log.error("Unknown InformationElement type: " + element.getClass());
        }
        return 0.0f;
    }
}
